﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Book_of_Shadow_Script
{
    public class CodePage:ICloneable
    {
        private SortedSet<Char> ignore;
        private SortedSet<Char> chars;
        private SortedSet<Int32> keys;
        private Int32 first;
        public CodePage(int f=0)
        {
            ignore = new SortedSet<Char>();
            chars = new SortedSet<Char>();
            keys = new SortedSet<Int32>();
            first=f;
        }
        public void AddChar(Char ch,Boolean ign)
        {
            if (!ign)
            {
                if (chars.Contains(ch)) return;
                if (ignore.Contains(ch)) return;
                chars.Add(ch);
                //if (keys.Count<chars.Count)
                {
                    int nk;
                    if (keys.Count > 0)
                    {
                        nk = keys.Last();
                        nk = next(nk);
                        if (nk < 0) throw new Exception("Too many character");
                    }
                    else nk = this.first;
                    keys.Add(nk);
                }
            }
            else
            {
                if (chars.Contains(ch))
                {
                    chars.Remove(ch);
                }
                if (ignore.Contains(ch)) return;
                ignore.Add(ch);
            }
        }
        public String[] Output()
        {
            String[] strs = new String[keys.Count];
            int i = 0;
            foreach (int key in keys)
            {
                strs[i] = String.Format("{0:X}={1}", key, chars.ElementAt(i));
                //strs[i] = String.Format("{0}", chars.ElementAt(i));
                i++;
            }
            return strs;
        }
        public String OutputIgnore()
        {
            StringBuilder sb = new StringBuilder();
            foreach (char ch in ignore)
                sb.Append(ch);
            return sb.ToString();
        }
        public void Clear(int f)
        {
            first = f;
            chars.Clear();
            keys.Clear();
        }
        private int next(int n)
        {
            int area=(n&0xFF00)>>8, point=n&0xFF;
            if (area==0)
            {
                if (point<0x20) 
                    return 0x20;
                if (point<0x7e)
                    return point +1 ;
                if (point<0xa1)
                    return 0xa1;
                if (point < 0xdf)
                    return point + 1;
                return 0x8140;
            }
            if (area<0x81) return 0x8140;

            if (area >= 0x81 && area < 0xa0)
            {
                if (point < 0x40) return (area << 8) + 0x40;
                if (point < 0x7e) return (area << 8) + point + 1;
                if (point < 0x80) return (area << 8) + 0x80;
                if (point < 0xfc) return (area << 8) + point + 1;
                if (point >= 0xfc)
                {
                    int na = area + 1;
                    if (na > 0x9f) na = 0xe0;
                    return (na << 8) + 0x40;
                }
            }
            if (area <0xdf) return 0xe040;
            if (area >= 0xe0 && area < 0xf0)
            {
                if (point < 0x40) return (area << 8) + 0x40;
                if (point < 0x7e) return (area << 8) + point + 1;
                if (point < 0x80) return (area << 8) + 0x80;
                if (point < 0xfc) return (area << 8) + point + 1;
                if (point >= 0xfc)
                {
                    int na = area + 1;
                    if (na > 0xf0) return -1;
                    return (na << 8) + 0x40;
                }
            }
            return -1;
        }

        public int ByteCount(int key)
        {
            if (key<0x8140) 
                return 1;
            else 
                return 2;
        }
        public int ByteCount(Byte[] bytes,int i)
        {
            byte by = bytes[i];
            if ((by > 0x80 && by < 0xa0) || by > 0xdf)
                return 2;
            else
                return 1;
        }
        public object Clone()
        {
            CodePage ret = new CodePage(first);
            foreach (char ch in this.chars)
            {
                ret.chars.Add(ch);
            }
            foreach (char ch in this.ignore)
            {
                ret.ignore.Add(ch);
            }
            foreach (int key in this.keys)
            {
                ret.keys.Add(key);
            }
            return ret;
        }

        public char[] ByteToChar()
        {
            char[] ret = new char[0x10000];
            Array.Clear(ret, 0, 0xFFFF);
            int i=0;
            foreach (int key in this.keys)
            {
                ret[key] = this.chars.ElementAt(i);
                i++;
            }
            return ret;
        }

        public int[] CharToByte()
        {
            int[] ret = new int[0x10000];
            Array.Clear(ret, 0, 0xFFFF);
            int i=0;
            foreach (char ch in this.chars)
            {
                ret[ch] = this.keys.ElementAt(i);
                i++;
            }
            return ret;
        }

        internal void AddChar(int p, char p_2)
        {
            if (chars.Contains(p_2)) return;
            if (ignore.Contains(p_2)) return;
            this.keys.Add(p);
            this.chars.Add(p_2);
        }
    }

    class CustomEncoding : Encoding  //encoder from custom codepage
    {
        int[] ctb = null;
        char[] btc=null;
        private int ByteCount(int key)
        {
            if (key < 0x8140)
                return 1;
            else
                return 2;
        }
        private int ByteCount(Byte[] bytes, int i)
        {
            byte by = bytes[i];
            if ((by > 0x80 && by < 0xa0) || by > 0xdf)
                return 2;
            else
                return 1;
        }
        public CustomEncoding(int[] ctb,char[] btc)
        {
            this.ctb = new int[0x10000];
            this.btc = new char[0x10000];
            Array.Copy(ctb, this.ctb, 0x10000);
            Array.Copy(btc, this.btc, 0x10000);
        }
        public override int GetByteCount(char[] chars, int index, int count)
        {
            int cnt=0;
            for (int i=index;i<index+count;i++)
            {
                int key = ctb[chars[i]];
                if (key == 0) throw new UncodedChar(chars[i]);
                cnt += this.ByteCount(key);
            }
            cnt++;
            return cnt;
        }

        public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
        {
            int chend = charIndex+charCount;
            int j = byteIndex;
            Byte[] buf=new Byte[2];
            for (int i=charIndex;i<chend;i++)
            {
                char ch = chars[i];
                int key = ctb[ch];
                int k= this.ByteCount(key);
                int k2 = k;
                while (k>0)
                {
                    k--;
                    bytes[j + k] = (Byte)(key & 0xff);
                    key >>= 8;
                }
                j += k2;
            }
            bytes[j++] = 0; //末尾添0
            return j - byteIndex;
        }

        public override int GetCharCount(byte[] bytes, int index, int count)
        {
            int cnt = 0;
            int i = index, byend=index + count; 
            while (i < byend)
            {
                cnt++;
                i += this.ByteCount(bytes,i);
            }
            return cnt;
        }

        public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
        {
            int key,k;
            int i = byteIndex,byend=byteIndex + byteCount;
            int chi=charIndex;
            while (i < byend)
            {
                k = this.ByteCount(bytes, i);
                key = 0;
                for (int j = i; j < i + k; j++)
                {
                    key <<= 8;
                    key += bytes[j];
                }
                chars[chi] = btc[key];
                chi++;
                i += k;
            }
            return chi - charIndex;
        }

        public override int GetMaxByteCount(int charCount)
        {
            return charCount * 2;
        }

        public override int GetMaxCharCount(int byteCount)
        {
            return byteCount;
        }
    }

}
